#include "Subsystems/BlockBehaviors/SubsystemCollapsingBlockBehavior.h"
#include "Log.h"
#include "GameSingleton.h"
#include "Subsystems/SubsystemTerrain.h"
#include "Subsystems/SubsystemGameInfo.h"
#include "Subsystems/SubsystemMovingBlocks.h"

#include "Blocks/SandBlock.h"
#include "Blocks/GravelBlock.h"
#include "Blocks/TrapdoorBlock.h"
#include "Blocks/FluidBlock.h"
#include "Blocks/PistonHeadBlock.h"
#include "Blocks/SoilBlock.h"

#include "Managers/GameManager.h"
#include "Managers/BlocksManager.h"

namespace PocketSurvival
{
    static const std::string IdString = "CollapsingBlock";

    SubsystemCollapsingBlockBehavior::SubsystemCollapsingBlockBehavior()
    {
        m_handleBlocks.push_back(SandBlock::Index());
        m_handleBlocks.push_back(GravelBlock::Index());
    }

    void SubsystemCollapsingBlockBehavior::onNeighborBlockChanged(int32_t x, int32_t y, int32_t z, int32_t neighborX, int32_t neighborY, int32_t neighborZ)
    {
        if(m_gameInfo->environmentBehaviorMode == EnvironmentBehaviorMode::Living)
        {
            tryCollapseColumn(Point3(x, y, z));
        }
    }

    void SubsystemCollapsingBlockBehavior::movingBlocksCollidedWithTerrain(MovingBlockSet *movingBlockSet, const Point3 &p)
    {
        if(movingBlockSet->Id == IdString)
        {
            int32_t cellValue = m_terrain->getCellValue(p);
            if(isCollapseSupportBlock(cellValue))
            {
                movingBlockSet->stop();
            }
            else if(isCollapseDestructibleBlock(cellValue))
            {
                m_terrain->destroyCell(0, p.posX, p.posY, p.posZ, 0, false);
            }
        }
    }

    void SubsystemCollapsingBlockBehavior::movingBlocksStopped(MovingBlockSet *movingBlockSet)
    {
        if(movingBlockSet->Id == IdString)
        {
            Point3 p = Terrain::ToCell(MathUtils::Round(movingBlockSet->Position.posX), MathUtils::Round(movingBlockSet->Position.posY), MathUtils::Round(movingBlockSet->Position.posZ));
            for(MovingBlock &item : movingBlockSet->Blocks)
            {
                Point3 point = p + item.offset;
                m_terrain->destroyCell(0, point.posX, point.posY, point.posZ, item.value, false);
            }
            m_movingBlocks->removeMovingBlockSet(movingBlockSet);
        }
    }

    void SubsystemCollapsingBlockBehavior::tryCollapseColumn(const Point3 &p)
    {
        if(p.posY <= 0)
        {
            return;
        }
        int32_t cellValue = m_terrain->getCellValue(p.posX, p.posY - 1, p.posZ);
        if(isCollapseSupportBlock(cellValue))
        {
            return;
        }
        std::vector<MovingBlock> list;
        for(int32_t index = p.posY; index < 256; index++)
        {
            int32_t cellValue2 = m_terrain->getCellValue(p.posX, index, p.posZ);
            if(isCollapsibleBlock(cellValue2) == false)
            {
                break;
            }
            list.emplace_back(Point3(0, index - p.posY, 0), cellValue2);
        }
        int32_t moveToY = list.size();
        moveToY = -moveToY - 1;

        // 方块移除现在再 SubsystemMovingBlocks 移除，防止多线程冲突
        if(list.size() != 0 && m_movingBlocks->addMovingBlockSet(Vector3(p), Vector3(p.posX, moveToY, p.posZ), 0.0f, 10.0f, 0.7f, FastVector2::Zero, list, IdString, nullptr, true) != nullptr)
        {
            // for(MovingBlock &item : list)
            // {
            //     Point3 point = p + item.offset;
            //     m_terrain->changeOldCell(point, item.value, 0);
            // }
        }
    }

    bool SubsystemCollapsingBlockBehavior::isCollapsibleBlock(int32_t value)
    {
        int32_t content = Terrain::ExtractContents(value);
        for(int32_t theValue : m_handleBlocks)
        {
            if(theValue == content)
            {
                return true;
            }
        }
        return false;
    }

    bool SubsystemCollapsingBlockBehavior::isCollapseSupportBlock(int32_t value)
    {
        int32_t num = Terrain::ExtractContents(value);
        if(num != 0)
        {
            int32_t data = Terrain::ExtractData(value);
            Block *block = GameSingleton::blocksManager->blocks[num];
            if(block->isBlock<TrapdoorBlock*>())
            {
                if(TrapdoorBlock::GetUpsideDown(data))
                {
                    return (TrapdoorBlock::GetOpen(data) == false);
                }
                return false;
            }
            if(block->getIndex() == PistonHeadBlock::Index())
            {
                return true;
            }
            if(block->isFaceTransparent(4, value))
            {
                return block->isBlock<SoilBlock*>();
            }
            return true;
        }
        return false;
    }

    bool SubsystemCollapsingBlockBehavior::isCollapseDestructibleBlock(int32_t value)
    {
        int32_t num = Terrain::ExtractContents(value);
        Block *block = GameSingleton::blocksManager->blocks[num];
        if(block->isBlock<TrapdoorBlock*>())
        {
            int32_t data = Terrain::ExtractData(value);
            if(TrapdoorBlock::GetUpsideDown(data) && TrapdoorBlock::GetOpen(data))
            {
                return false;
            }
        }
        else if(block->isBlock<FluidBlock*>())
        {
            return false;
        }
        return true;
    }

    void SubsystemCollapsingBlockBehavior::load(const nlohmann::json &json)
    {
        m_terrain = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>();
        m_gameInfo = GameSingleton::singletonPtr->getSingleton<SubsystemGameInfo>();
        m_movingBlocks = GameSingleton::singletonPtr->getSingleton<SubsystemMovingBlocks>();

        m_movingBlocks->addCollidedWithTerrainEvent(std::bind(&SubsystemCollapsingBlockBehavior::movingBlocksCollidedWithTerrain, this, std::placeholders::_1, std::placeholders::_2));
        m_movingBlocks->addStoppedEvent(std::bind(&SubsystemCollapsingBlockBehavior::movingBlocksStopped, this, std::placeholders::_1));
    }

    static const std::string subsystemName = "CollapsingBlockBehavior";
    const std::string &SubsystemCollapsingBlockBehavior::getName() const
    {
        return subsystemName;
    }

}